import numpy as np
import pandas as pd
import math
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode(connected=True)
import shutil
columns = shutil.get_terminal_size().columns
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.metrics import average_precision_score
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn import svm
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.linear_model import SGDClassifier
from sklearn.linear_model import RidgeClassifier
import imblearn
from imblearn.under_sampling import RandomUnderSampler
from imblearn.over_sampling import RandomOverSampler
from imblearn.under_sampling import TomekLinks
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import NearMiss
from IPython.display import Javascript
from IPython.display import display
# #Configura a GPU para funciona com o TensorFlow
# physical_devices = tf.config.list_physical_devices('GPU')
# tf.config.experimental.set_memory_growth(physical_devices[0], True)
# print(physical_devices)
# print("Número de GPU's : ", len(tf.config.list_physical_devices("GPU")))
#pip freeze > requirements.txt
data = pd.read_csv('creditcard.csv')
data
| Time | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | ... | V21 | V22 | V23 | V24 | V25 | V26 | V27 | V28 | Amount | Class | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.0 | -1.359807 | -0.072781 | 2.536347 | 1.378155 | -0.338321 | 0.462388 | 0.239599 | 0.098698 | 0.363787 | ... | -0.018307 | 0.277838 | -0.110474 | 0.066928 | 0.128539 | -0.189115 | 0.133558 | -0.021053 | 149.62 | 0 |
| 1 | 0.0 | 1.191857 | 0.266151 | 0.166480 | 0.448154 | 0.060018 | -0.082361 | -0.078803 | 0.085102 | -0.255425 | ... | -0.225775 | -0.638672 | 0.101288 | -0.339846 | 0.167170 | 0.125895 | -0.008983 | 0.014724 | 2.69 | 0 |
| 2 | 1.0 | -1.358354 | -1.340163 | 1.773209 | 0.379780 | -0.503198 | 1.800499 | 0.791461 | 0.247676 | -1.514654 | ... | 0.247998 | 0.771679 | 0.909412 | -0.689281 | -0.327642 | -0.139097 | -0.055353 | -0.059752 | 378.66 | 0 |
| 3 | 1.0 | -0.966272 | -0.185226 | 1.792993 | -0.863291 | -0.010309 | 1.247203 | 0.237609 | 0.377436 | -1.387024 | ... | -0.108300 | 0.005274 | -0.190321 | -1.175575 | 0.647376 | -0.221929 | 0.062723 | 0.061458 | 123.50 | 0 |
| 4 | 2.0 | -1.158233 | 0.877737 | 1.548718 | 0.403034 | -0.407193 | 0.095921 | 0.592941 | -0.270533 | 0.817739 | ... | -0.009431 | 0.798278 | -0.137458 | 0.141267 | -0.206010 | 0.502292 | 0.219422 | 0.215153 | 69.99 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 284802 | 172786.0 | -11.881118 | 10.071785 | -9.834783 | -2.066656 | -5.364473 | -2.606837 | -4.918215 | 7.305334 | 1.914428 | ... | 0.213454 | 0.111864 | 1.014480 | -0.509348 | 1.436807 | 0.250034 | 0.943651 | 0.823731 | 0.77 | 0 |
| 284803 | 172787.0 | -0.732789 | -0.055080 | 2.035030 | -0.738589 | 0.868229 | 1.058415 | 0.024330 | 0.294869 | 0.584800 | ... | 0.214205 | 0.924384 | 0.012463 | -1.016226 | -0.606624 | -0.395255 | 0.068472 | -0.053527 | 24.79 | 0 |
| 284804 | 172788.0 | 1.919565 | -0.301254 | -3.249640 | -0.557828 | 2.630515 | 3.031260 | -0.296827 | 0.708417 | 0.432454 | ... | 0.232045 | 0.578229 | -0.037501 | 0.640134 | 0.265745 | -0.087371 | 0.004455 | -0.026561 | 67.88 | 0 |
| 284805 | 172788.0 | -0.240440 | 0.530483 | 0.702510 | 0.689799 | -0.377961 | 0.623708 | -0.686180 | 0.679145 | 0.392087 | ... | 0.265245 | 0.800049 | -0.163298 | 0.123205 | -0.569159 | 0.546668 | 0.108821 | 0.104533 | 10.00 | 0 |
| 284806 | 172792.0 | -0.533413 | -0.189733 | 0.703337 | -0.506271 | -0.012546 | -0.649617 | 1.577006 | -0.414650 | 0.486180 | ... | 0.261057 | 0.643078 | 0.376777 | 0.008797 | -0.473649 | -0.818267 | -0.002415 | 0.013649 | 217.00 | 0 |
284807 rows × 31 columns
features = list(data.columns)
features.remove('Class')
# Statistical descriptions of the features
features_stat = data.drop(['Class'], axis = 1)
features_stat.describe()
| Time | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | ... | V20 | V21 | V22 | V23 | V24 | V25 | V26 | V27 | V28 | Amount | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 284807.000000 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | ... | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 284807.000000 |
| mean | 94813.859575 | 1.168375e-15 | 3.416908e-16 | -1.379537e-15 | 2.074095e-15 | 9.604066e-16 | 1.487313e-15 | -5.556467e-16 | 1.213481e-16 | -2.406331e-15 | ... | 6.406204e-16 | 1.654067e-16 | -3.568593e-16 | 2.578648e-16 | 4.473266e-15 | 5.340915e-16 | 1.683437e-15 | -3.660091e-16 | -1.227390e-16 | 88.349619 |
| std | 47488.145955 | 1.958696e+00 | 1.651309e+00 | 1.516255e+00 | 1.415869e+00 | 1.380247e+00 | 1.332271e+00 | 1.237094e+00 | 1.194353e+00 | 1.098632e+00 | ... | 7.709250e-01 | 7.345240e-01 | 7.257016e-01 | 6.244603e-01 | 6.056471e-01 | 5.212781e-01 | 4.822270e-01 | 4.036325e-01 | 3.300833e-01 | 250.120109 |
| min | 0.000000 | -5.640751e+01 | -7.271573e+01 | -4.832559e+01 | -5.683171e+00 | -1.137433e+02 | -2.616051e+01 | -4.355724e+01 | -7.321672e+01 | -1.343407e+01 | ... | -5.449772e+01 | -3.483038e+01 | -1.093314e+01 | -4.480774e+01 | -2.836627e+00 | -1.029540e+01 | -2.604551e+00 | -2.256568e+01 | -1.543008e+01 | 0.000000 |
| 25% | 54201.500000 | -9.203734e-01 | -5.985499e-01 | -8.903648e-01 | -8.486401e-01 | -6.915971e-01 | -7.682956e-01 | -5.540759e-01 | -2.086297e-01 | -6.430976e-01 | ... | -2.117214e-01 | -2.283949e-01 | -5.423504e-01 | -1.618463e-01 | -3.545861e-01 | -3.171451e-01 | -3.269839e-01 | -7.083953e-02 | -5.295979e-02 | 5.600000 |
| 50% | 84692.000000 | 1.810880e-02 | 6.548556e-02 | 1.798463e-01 | -1.984653e-02 | -5.433583e-02 | -2.741871e-01 | 4.010308e-02 | 2.235804e-02 | -5.142873e-02 | ... | -6.248109e-02 | -2.945017e-02 | 6.781943e-03 | -1.119293e-02 | 4.097606e-02 | 1.659350e-02 | -5.213911e-02 | 1.342146e-03 | 1.124383e-02 | 22.000000 |
| 75% | 139320.500000 | 1.315642e+00 | 8.037239e-01 | 1.027196e+00 | 7.433413e-01 | 6.119264e-01 | 3.985649e-01 | 5.704361e-01 | 3.273459e-01 | 5.971390e-01 | ... | 1.330408e-01 | 1.863772e-01 | 5.285536e-01 | 1.476421e-01 | 4.395266e-01 | 3.507156e-01 | 2.409522e-01 | 9.104512e-02 | 7.827995e-02 | 77.165000 |
| max | 172792.000000 | 2.454930e+00 | 2.205773e+01 | 9.382558e+00 | 1.687534e+01 | 3.480167e+01 | 7.330163e+01 | 1.205895e+02 | 2.000721e+01 | 1.559499e+01 | ... | 3.942090e+01 | 2.720284e+01 | 1.050309e+01 | 2.252841e+01 | 4.584549e+00 | 7.519589e+00 | 3.517346e+00 | 3.161220e+01 | 3.384781e+01 | 25691.160000 |
8 rows × 30 columns
Classificação de transações como autênticas ou fraudulentas. Para sermos precisos, dados os dados sobre Time, Amount e recursos transformados V1 a V28 para uma determinada transação, nossa meta é classificar corretamente a transação como autêntica ou fraudulenta. Empregamos diferentes técnicas para criar modelos de classificação e compará-los por meio de várias métricas de avaliação.
Responder às seguintes perguntas usando ferramentas e técnicas de aprendizado de máquina e estatísticas.
Quando uma transação fraudulenta é feita, ela é seguida logo por uma ou mais transações fraudulentas? Em outras palavras, os invasores fazem transações fraudulentas consecutivas em um curto espaço de tempo?
O valor de uma transação fraudulenta é geralmente maior do que o de uma transação autêntica?
Há alguma indicação nos dados de que as transações fraudulentas ocorrem em um período de alta transação?
Os dados mostram que o número de transações é alto em alguns intervalos de tempo e baixo em outros. A ocorrência de fraudes está relacionada a esses intervalos de tempo?
Há alguns pontos de tempo que exibem um número alto de transações fraudulentas. Isso se deve ao alto número de transações totais ou a algum outro motivo?
Nesta parte, classificaremos as transações como autênticas ou fraudulentas com base nas informações disponíveis sobre os recursos independentes (tempo, valor e as variáveis transformadas V1-V28). Um problema com o conjunto de dados é que ele é altamente desequilibrado em termos da variável-alvo Classe. Assim, corremos o risco de treinar os modelos com uma amostra representativa de transações fraudulentas de tamanho extremamente pequeno. Empregamos diferentes abordagens para lidar com esse problema. O desempenho de cada modelo é verificado por meio de várias métricas de avaliação e está resumido em uma tabela.
Qualquer previsão sobre uma variável de destino categórica binária se enquadra em uma das quatro categorias:
| Estado real / Estado previsto $\rightarrow$ | Positivo | Negativo | |
|---|---|---|---|
| Positivo | Verdadeiro positivo | Falso negativo | Negativo |
| Negativo | Falso positivo | Verdadeiro negativo |
Deixe que TP, TN, FP e FN denotem, respectivamente, o número de verdadeiros positivos, verdadeiros negativos, falso positivos e falso negativos entre as previsões feitas por um determinado modelo de classificação. A seguir, apresentamos as definições de algumas métricas de avaliação com base nessas quatro quantidades.
$$\text{Accuracy} = \frac{\text{Number of correct predictions}}{\text{Number of total predictions}} = \frac{TP + TN}{TP + TN + FP + FN}$$em que $\beta$ é um fator positivo, escolhido de forma que o Recall seja $\beta$ vezes mais importante que a Precisão na análise. As escolhas populares de $\beta$ são $0,5$, $1$ e $2$.
Considere as seguintes quantidades:
\begin{align*} &\text{True Positive Rate (TPR)} = \frac{\text{Number of true positive predictions}}{\text{Number of total positive cases}} = \frac{TP}{TP + FN}\\\\ &\text{False Positive Rate (FPR)} = \frac{\text{Number of false positive predictions}}{\text{Number of total negative cases}} = \frac{FP}{FP + TN} \end{align*}A curva ROC (Receiver Operating Characteristic, Característica de Operação do Receptor) é obtida plotando-se a TPR em relação à FPR para vários valores de probabilidade de limite. A área sob a curva ROC (ROC-AUC) serve como uma métrica de avaliação válida.
Da mesma forma, a curva Precision-Recall (PR) é obtida plotando-se a Precision em relação à Recall para um número de valores de probabilidade de limite. A área sob a curva PR (PR-AUC) também é uma métrica de avaliação válida. Outra métrica amplamente usada nesse sentido é a precisão média (Average Precision, AP), que é uma média ponderada de precisões em cada limite, com os pesos sendo o aumento na recuperação do limite anterior.
O Matthews Correlation Coefficient (MCC) é calculado pela fórmula:
MCC = (TP * TN - FP * FN) / sqrt((TP + FP) * (TP + FN) * (TN + FP) * (TN + FN))
O Matthews Correlation Coefficient (MCC) é calculado pela fórmula: $MCC = (TP \times TN - FP \times FN) / \sqrt{(TP + FP) \times (TP + FN) \times (TN + FP) \times (TN + FN)}$
Diferentemente das métricas anteriores, MCC varia de $-1$ (pior cenário) a $1$ (melhor cenário: previsão perfeita).
Observe que Recall e Sensibilidade são essencialmente a mesma quantidade.
Entre as métricas discutidas, algumas boas opções para avaliar modelos, em especial para conjuntos de dados desequilibrados, são MCC e $F_1$-Score, enquanto Precision e Recall também fornecem informações úteis. Não daremos muita importância à métrica Accuracy neste projeto, pois ela produz conclusões errôneas quando as classes não estão equilibradas. No problema em questão, o falso negativo (uma transação fraudulenta classificada como autêntica) é mais perigoso do que o falso positivo (uma transação autêntica classificada como fraudulenta), pois, no primeiro caso, o fraudador pode causar mais danos financeiros, enquanto no segundo caso o banco pode verificar a autenticidade da transação do usuário do cartão depois de tomar as medidas necessárias para proteger o cartão. Considerando esse fato, damos ao $F_2$-Score uma importância especial na avaliação dos modelos.
EvalMetricLabels = ['MCC', 'F1-Score', 'F2-Score', 'Recall', 'Precision',
'FM index', 'Specificity', 'G-mean', 'F0.5-Score', 'Accuracy']
# Separação das variáveis independentes e da variável-alvo
y = data['Class'] # alvo
X = data.drop('Class', axis = 1) # independente
# Construção do conjunto de treinamento e do conjunto de teste
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 25)
data_train = pd.concat([X_train, y_train], axis = 1)
data_train_authentic = data_train[data_train['Class'] == 0]
data_train_fraudulent = data_train[data_train['Class'] == 1]
# Valor vs. tempo para transações autênticas e fraudulentas no conjunto de treinamento
class_list_train = list(y_train)
fraud_status_train = []
for i in range(len(class_list_train)):
fraud_status_train.append(bool(class_list_train[i]))
fig1 = px.scatter(data_train,
x = 'Time',
y = 'Amount',
facet_col = fraud_status_train,
color = fraud_status_train,
title = 'Amount vs Time for the training set',
template = 'ggplot2'
)
fig1.show()
Pegamos um subconjunto da classe majoritária para equilibrar o conjunto de dados de treinamento.
Vantagem: Melhora o tempo de execução e resolve qualquer problema de armazenamento devido ao grande conjunto de dados de aprendizado.
Desvantagem: Ignora um pedaço de informação que poderia ser impactante na análise e usa apenas uma amostra representativa da classe majoritária, o que não garante que reflita a mesma com precisão.
data_train_authentic_under = data_train_authentic.sample(len(data_train_fraudulent))
data_train_under = pd.concat([data_train_authentic_under, data_train_fraudulent], axis = 0)
X_train_under = data_train_under.drop('Class', axis = 1)
y_train_under = data_train_under['Class']
print('Class frequencies after under-sampling:')
print(y_train_under.value_counts())
y_train_under.value_counts().plot(kind = 'bar', title = 'Class frequencies after under-sampling')
Class frequencies after under-sampling: 0 380 1 380 Name: Class, dtype: int64
<Axes: title={'center': 'Class frequencies after under-sampling'}>
# Valor vs. tempo para transações autênticas e fraudulentas no conjunto de treinamento após subamostragem aleatória
class_list = list(y_train_under)
fraud_status = []
for i in range(len(class_list)):
fraud_status.append(bool(class_list[i]))
fig1 = px.scatter(data_train_under,
x = 'Time',
y = 'Amount',
facet_col = fraud_status,
color = fraud_status,
title = 'Amount vs Time for the training set after random under-sampling',
template = 'ggplot2'
)
fig1.show()
Comparando com os mesmos gráficos do conjunto de dados completo, vemos que as transações autênticas de alto valor não são representadas na amostra retirada da classe majoritária. Isso indica uma desvantagem geral das técnicas de subamostragem, que jogam fora uma grande parte das informações da classe majoritária e não representam as mesmas com precisão.
data_train_fraudulent_over = data_train_fraudulent.sample(len(data_train_authentic), replace = 'True')
data_train_over = pd.concat([data_train_authentic, data_train_fraudulent_over], axis = 0)
X_train_over = data_train_over.drop('Class', axis = 1)
y_train_over = data_train_over['Class']
print('Class frequencies after over-sampling:')
print(y_train_over.value_counts())
y_train_over.value_counts().plot(kind = 'bar', title = 'Class frequencies after over-sampling')
Class frequencies after over-sampling: 0 227465 1 227465 Name: Class, dtype: int64
<Axes: title={'center': 'Class frequencies after over-sampling'}>
# Valor vs. tempo para transações autênticas e fraudulentas no conjunto de treinamento após amostragem aleatória
class_list = list(y_train_over)
fraud_status = []
for i in range(len(class_list)):
fraud_status.append(bool(class_list[i]))
fig1 = px.scatter(data_train_over,
x = 'Time',
y = 'Amount',
facet_col = fraud_status,
color = fraud_status,
title = 'Amount vs Time',
template = 'ggplot2'
)
fig1.show()
Esses gráficos se assemelham aos gráficos Amount vs Time correspondentes para o conjunto de dados completo com muito mais precisão do que os gráficos correspondentes para o conjunto de treinamento obtido da subamostragem aleatória.
imblearn_rus = RandomUnderSampler(random_state = 40, replacement = True)
X_train_rus, y_train_rus = imblearn_rus.fit_resample(X_train, y_train)
X_train_rus = pd.DataFrame(X_train_rus)
X_train_rus.columns = features
y_train_rus = pd.DataFrame(y_train_rus)
y_train_rus.columns = ['Class']
data_train_under_imblearn = pd.concat([X_train_rus, y_train_rus], axis = 1)
X_train_under_imblearn = data_train_under_imblearn.drop('Class', axis = 1)
y_train_under_imblearn = data_train_under_imblearn['Class']
print('Class frequencies after under-sampling via imbalanced-learn library:')
print(y_train_under_imblearn.value_counts())
y_train_under_imblearn.value_counts().plot(kind = 'bar',
title = 'Class frequencies after under-sampling via imbalanced-learn library')
Class frequencies after under-sampling via imbalanced-learn library: 0 380 1 380 Name: Class, dtype: int64
<Axes: title={'center': 'Class frequencies after under-sampling via imbalanced-learn library'}>
# Valor vs. tempo para transações autênticas e fraudulentas no conjunto de treinamento após subamostragem aleatória via imblearn
class_list = list(y_train_under_imblearn)
fraud_status = []
for i in range(len(class_list)):
fraud_status.append(bool(class_list[i]))
fig1 = px.scatter(data_train_under_imblearn,
x = 'Time',
y = 'Amount',
facet_col = fraud_status,
color = fraud_status,
title = 'Amount vs Time',
template = 'ggplot2'
)
fig1.show()
imblearn_ros = RandomOverSampler(random_state = 40)
X_train_ros, y_train_ros = imblearn_ros.fit_resample(X_train, y_train)
X_train_ros = pd.DataFrame(X_train_ros)
X_train_ros.columns = features
y_train_ros = pd.DataFrame(y_train_ros)
y_train_ros.columns = ['Class']
data_train_over_imblearn = pd.concat([X_train_ros, y_train_ros], axis = 1)
X_train_over_imblearn = data_train_over_imblearn.drop('Class', axis = 1)
y_train_over_imblearn = data_train_over_imblearn['Class']
print('Class frequencies after over-sampling via imbalanced-learn library:')
print(y_train_over_imblearn.value_counts())
y_train_over_imblearn.value_counts().plot(kind = 'bar',
title = 'Class frequencies after over-sampling via imbalanced-learn library')
Class frequencies after over-sampling via imbalanced-learn library: 0 227465 1 227465 Name: Class, dtype: int64
<Axes: title={'center': 'Class frequencies after over-sampling via imbalanced-learn library'}>
# Valor vs. tempo para transações autênticas e fraudulentas no conjunto de treinamento após amostragem aleatória via imblearn
class_list = list(y_train_over_imblearn)
fraud_status = []
for i in range(len(class_list)):
fraud_status.append(bool(class_list[i]))
fig1 = px.scatter(data_train_over_imblearn,
x = 'Time',
y = 'Amount',
facet_col = fraud_status,
color = fraud_status,
title = 'Amount vs Time',
template = 'ggplot2'
)
fig1.show()
smote = SMOTE()
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)
X_train_smote = pd.DataFrame(X_train_smote)
X_train_smote.columns = features
X_train_smote
y_train_smote = pd.DataFrame(y_train_smote)
y_train_smote.columns = ['Class']
y_train_smote
data_train_over_smote = pd.concat([X_train_smote, y_train_smote], axis = 1)
X_train_over_smote = data_train_over_smote.drop('Class', axis = 1)
y_train_over_smote = data_train_over_smote['Class']
print('Class frequencies after over-sampling via SMOTE:')
print(y_train_over_smote.value_counts())
y_train_over_smote.value_counts().plot(kind = 'bar', title = 'Class frequencies after over-sampling via SMOTE')
Class frequencies after over-sampling via SMOTE: 0 227465 1 227465 Name: Class, dtype: int64
<Axes: title={'center': 'Class frequencies after over-sampling via SMOTE'}>
# Valor vs. tempo para transações autênticas e fraudulentas no conjunto de treinamento após a amostragem excessiva via SMOTE
class_list = list(y_train_over_smote)
fraud_status = []
for i in range(len(class_list)):
fraud_status.append(bool(class_list[i]))
fig1 = px.scatter(data_train_over_smote,
x = 'Time',
y = 'Amount',
facet_col = fraud_status,
color = fraud_status,
title = 'Amount vs Time',
template = 'ggplot2'
)
fig1.show()